#ifndef __UTIL__MESSAGE_HPP__
#define __UTIL__MESSAGE_HPP__

#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>

namespace util {
  /** The following class are based on the logging stream in
      "Digging a Ditch: Writing a custom stream" by Paul Grenyer,
      13 March 2005
      http://www.paulgrenyer.dyndns.org/articles/digging%20a%20ditch.pdf
  */

  template<class charT, class traits = std::char_traits<charT> >
  class message_buf : public std::basic_streambuf<charT, traits> {
    typedef typename std::basic_streambuf<charT, traits>::int_type int_type;
    typedef std::vector<charT> buffer_type;

  public:
    message_buf() :
      out(0),
      log(0),
      buffer(),
      prepend(),
      prepend_length(0)
    {}

    ~message_buf() {
      sync();
    }

    void init(std::basic_streambuf<charT, traits>* out_sb = 0) {
      out = out_sb;
    }
    void set_log_sb(std::basic_streambuf<charT, traits>* log_sb = 0) {
      log = log_sb;
    }
    void set_prepend(std::basic_string<charT> s) {
      prepend = s;
      prepend_length = prepend.size();
    }
    virtual int_type overflow(int_type c) {
      if (!traits::eq_int_type(c, traits::eof()))
	buffer.push_back(c);
      return traits::not_eof(c);
    }
    virtual std::streamsize xsputn(const charT* s, std::streamsize num) {
      std::copy(s, s + num, std::back_inserter<buffer_type>(buffer));
      return num;
    }

  protected:
    std::basic_streambuf<charT, traits >* out;
    std::basic_streambuf<charT, traits >* log;
    buffer_type                           buffer;
    std::basic_string<charT>              prepend;
    unsigned int                          prepend_length;

    virtual int sync() {
      if (!buffer.empty()) {
	if (out) {
	  if (prepend_length) out->sputn(&prepend[0], prepend_length);
	  out->sputn(&buffer[0], static_cast<std::streamsize>(buffer.size()));
	}
	if (log) {
	  if (prepend_length) log->sputn(&prepend[0], prepend_length);
	  log->sputn(&buffer[0], static_cast<std::streamsize>(buffer.size()));
	}
	buffer.clear();
      }
      return 0;
    }
  };

  template<class charT, class traits = std::char_traits<charT> >
  class fatal_message_buf : public message_buf<charT, traits> {
  public:
    fatal_message_buf() : message_buf<charT, traits>() {}
    ~fatal_message_buf() {
      sync();
    }
  protected:
    virtual int sync() {
      message_buf<charT, traits>::sync();
      std::exit(-1);
    }
  };

  template<class charT, class traits> class exception_stream;

  template <class charT, class traits = std::char_traits<charT> >
  class util_exception : public std::exception {
  public:
    util_exception(std::basic_string<charT> msg, exception_stream<charT, traits>* e) :
      std::exception(),
      s(msg),
      exc(e)
    {}
    virtual ~util_exception() throw() {
      exc->clear();
    }
    virtual const char* what() {
      return s.c_str();
    }
  protected:
    std::basic_string<charT> s;
    exception_stream<charT, traits>* exc;
  };

  template<class charT, class traits = std::char_traits<charT> >
  class exception_message_buf : public message_buf<charT, traits> {
  public:
    exception_message_buf() :
      message_buf<charT, traits>(),
      exc(0)
    {}
    ~exception_message_buf() {
      sync();
    }
    void set_exception_stream(exception_stream<charT, traits>* e) {
      exc = e;
    }
  protected:
    virtual int sync() throw(util_exception<charT>) {
      if (!message_buf<charT, traits>::buffer.empty()) {
	if (message_buf<charT, traits>::log) {
	  if (message_buf<charT, traits>::prepend_length) message_buf<charT, traits>::log->sputn(&message_buf<charT, traits>::prepend[0], message_buf<charT, traits>::prepend_length);
	  message_buf<charT, traits>::log->sputn(&message_buf<charT, traits>::buffer[0], static_cast<std::streamsize>(message_buf<charT, traits>::buffer.size()));
	}
	std::basic_string<charT> s;
	for (unsigned int i = 0; i < message_buf<charT, traits>::buffer.size(); ++i)
	  s.push_back(message_buf<charT, traits>::buffer[i]);
	util_exception<charT> e(s, exc);
	message_buf<charT, traits>::buffer.clear();
	throw e;
      }
      return 0;
    }
    exception_stream<charT, traits>* exc;
  };

  template<class charT, class traits = std::char_traits<charT> >
  class message_buf_init {
  public:
    message_buf<charT, traits>* buf() {
      return &mbuf;
    }
  private:
    message_buf<charT, traits> mbuf;
  };

  template<class charT, class traits = std::char_traits<charT> >
  class fatal_message_buf_init {
  public:
    fatal_message_buf<charT, traits>* buf() {
      return &mbuf;
    }
  private:
    fatal_message_buf<charT, traits> mbuf;
  };

  template<class charT, class traits = std::char_traits<charT> >
  class exception_message_buf_init {
  public:
    exception_message_buf<charT, traits>* buf() {
      return &mbuf;
    }
  private:
    exception_message_buf<charT, traits> mbuf;
  };

  template<class charT, class traits = std::char_traits<charT> >
  class message_stream :
    private virtual message_buf_init<charT, traits>,
    public std::basic_ostream<charT, traits>
  {
    typedef message_buf_init<charT, traits> message_buf_init;

  public:
    message_stream(std::basic_string<charT> pre = std::basic_string<charT>(), std::basic_ostream<charT, traits>& out = std::cout) :
      message_buf_init(),
      std::basic_ostream<charT, traits>(message_buf_init::buf())
    {
      message_buf_init::buf()->init(out.rdbuf());
      prepend(pre);
    }

    void log(std::basic_ostream<charT, traits>& log) {
      message_buf_init::buf()->set_log_sb(log.rdbuf());
    }

    void prepend(std::basic_string<charT> prepend) {
      message_buf_init::buf()->set_prepend(prepend);
    }
  };

  template<class charT, class traits = std::char_traits<charT> >
  class warning_stream : public message_stream<charT, traits> {
  public:
    warning_stream(std::basic_ostream<charT, traits>& out = std::cout) :
      message_stream<charT, traits>("WARNING: ", out)
    {}
  };

  template<class charT, class traits = std::char_traits<charT> >
  class error_stream : public message_stream<charT, traits> {
  public:
    error_stream(std::basic_ostream<charT, traits>& out = std::cout) :
      message_stream<charT, traits>("ERROR: ", out)
    {}
  };

  template<class charT, class traits = std::char_traits<charT> >
  class fatal_error_stream :
    private virtual fatal_message_buf_init<charT, traits>,
    public std::basic_ostream<charT, traits>
  {
    typedef fatal_message_buf_init<charT, traits> fatal_message_buf_init;

  public:
    fatal_error_stream(std::basic_ostream<charT, traits>& out = std::cout) :
      fatal_message_buf_init(),
      std::basic_ostream<charT, traits>(fatal_message_buf_init::buf())
    {
      fatal_message_buf_init::buf()->init(out.rdbuf());
      prepend("FATAL ERROR: ");
    }

    void log(std::basic_ostream<charT, traits>& log) {
      fatal_message_buf_init::buf()->set_log_sb(log.rdbuf());
    }

    void prepend(std::basic_string<charT> prepend) {
      fatal_message_buf_init::buf()->set_prepend(prepend);
    }
  };

  template<class charT, class traits = std::char_traits<charT> >
  class exception_stream :
    private virtual exception_message_buf_init<charT, traits>,
    public std::basic_ostream<charT, traits>
  {
    typedef exception_message_buf_init<charT, traits> exception_message_buf_init;

  public:
    exception_stream() :
      exception_message_buf_init(),
      std::basic_ostream<charT, traits>(exception_message_buf_init::buf())
    {
      prepend("EXCEPTION: ");
      exception_message_buf_init::buf()->set_exception_stream(this);
      this->exceptions(
        std::ios_base::badbit|std::ios_base::failbit|std::ios_base::eofbit
      );
    }

    void log(std::basic_ostream<charT, traits>& log) {
      exception_message_buf_init::buf()->set_log_sb(log.rdbuf());
    }

    void prepend(std::basic_string<charT> prepend) {
      exception_message_buf_init::buf()->set_prepend(prepend);
    }
  };

  static message_stream<char>     msg;
  static warning_stream<char>     warn;
  static error_stream<char>       err;
  static fatal_error_stream<char> ferr;
  static exception_stream<char>   exc;

  typedef util_exception<char> mexc;
}

#endif
